package com.gitee.codegen.util;

import com.gitee.codegen.config.GeneratorProperties;
import com.gitee.codegen.entity.ColumnEntity;
import com.gitee.codegen.entity.TableEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 代码生成器   工具类
 */
@Slf4j
public class GenUtils {
    private static String currentTableName;

    public static List<String> getTemplates() {
        List<String> templates = new ArrayList<String>();
        templates.add("template/Entity.java.vm");
        templates.add("template/Dao.xml.vm");

        templates.add("template/menu.sql.vm");

        templates.add("template/Service.java.vm");
        templates.add("template/ServiceImpl.java.vm");
        templates.add("template/Controller.java.vm");
        templates.add("template/Dao.java.vm");

        templates.add("template/index.vue.vm");
        templates.add("template/add-or-update.vue.vm");
        return templates;
    }

    public static List<String> getTemplatesJpa() {
        List<String> templates = new ArrayList<String>();
        templates.add("template/jpa/Entity.java.vm");
        templates.add("template/jpa/Service.java.vm");
        templates.add("template/jpa/ServiceImpl.java.vm");
        templates.add("template/jpa/Controller.java.vm");
        templates.add("template/jpa/Repository.java.vm");
        templates.add("template/jpa/SearchDto.java.vm");
        return templates;
    }

    public static List<String> getMongoChildTemplates() {
        List<String> templates = new ArrayList<String>();
        templates.add("template/MongoChildrenEntity.java.vm");
        return templates;
    }

    /**
     * 生成代码
     */
    public static void generatorCode(TableEntity tableEntity,
                                     List<ColumnEntity> columns, ZipOutputStream zip,
                                     GeneratorProperties config, String moduleName) throws IOException {
        final VelocityContext context = getVelocityContext(tableEntity, columns, config, moduleName);

        //获取模板列表
        if (config.getTemplates() == null) {
            config.setTemplates(getTemplatesJpa());
        }
        List<String> templates = config.getTemplates();
        for (String template : templates) {
            //渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(template, "UTF-8");
            tpl.merge(context, sw);

            //添加到zip
            zip.putNextEntry(new ZipEntry(getFileName(template, tableEntity.getClassName(), config.getPackages(), moduleName)));

            IOUtils.write(sw.toString(), zip, StandardCharsets.UTF_8);
            sw.close();
            zip.closeEntry();
        }
    }

    /**
     * 生成代码到指定位置
     */
    public static void generatorCode(TableEntity tableEntity, List<ColumnEntity> columns,
                                     GeneratorProperties config, String moduleName) throws IOException {
        final VelocityContext context = getVelocityContext(tableEntity, columns, config, moduleName);

        // 生成位置
        String generateLocation = config.getGenerateLocation();

        //获取模板列表
        if (config.getTemplates() == null) {
            config.setTemplates(getTemplatesJpa());
        }
        List<String> templates = config.getTemplates();
        for (String template : templates) {
            //渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(template, "UTF-8");
            tpl.merge(context, sw);

            final String fileName = getFileName(template, tableEntity.getClassName(), config.getPackages(), moduleName);
            final File file = new File(generateLocation + File.separator + fileName);
            if (file.exists()) {
                log.warn("文件存在不进行生成 filePath={}", file.getAbsolutePath());
            } else {
                FileUtils.writeStringToFile(file, sw.toString(), StandardCharsets.UTF_8);
            }
        }
    }

    /**
     * 获取模板上下文
     */
    private static VelocityContext getVelocityContext(TableEntity tableEntity, List<ColumnEntity> columns, GeneratorProperties config,
                                                      String moduleName) {
        if (moduleName == null) {
            moduleName = config.getModuleName();
        }

        columns = columns.stream().filter(it -> !config.getExcludeColumn().contains(it.getColumnName())).collect(Collectors.toList());

        //配置信息
        boolean hasBigDecimal = false;
        boolean hasList = false;
        //表信息
        //表名转换成Java类名
        String className = tableToJava(tableEntity.getTableName(), config.getTablePrefix());
        tableEntity.setClassName(className);
        tableEntity.setClassNameLowercase(StringUtils.uncapitalize(className));

        //列信息
        List<ColumnEntity> columsList = new ArrayList<>();
        final HashSet<String> importSet = new HashSet<>();
        for (ColumnEntity columnEntity : columns) {
            //列名转换成Java属性名
            String attrName = columnToJava(columnEntity.getColumnName());
            columnEntity.setAttrName(attrName);
            columnEntity.setAttrNameLowercase(StringUtils.uncapitalize(attrName));

            //列的数据类型，转换成Java类型
            String attrType = config.getTypeConvert().get(columnEntity.getDataType());
            if (attrType == null) {
                attrType = columnToJava(columnEntity.getDataType());
            }

            columnEntity.setAttrType(attrType);

            if (attrType.equals("BigDecimal")) {
                importSet.add("\nimport java.math.BigDecimal;");
                importSet.add("\nimport javax.validation.constraints.Digits;");
                hasBigDecimal = true;
            }
            if (attrType.equals("Date")) {
                importSet.add("\nimport java.util.Date;");
            }
            if (!hasList && "array".equals(columnEntity.getExtra())) {
                hasList = true;
            }
            //是否主键
            if ("PRI".equalsIgnoreCase(columnEntity.getColumnKey()) && tableEntity.getPk() == null) {
                tableEntity.setPk(columnEntity);
            }

            columsList.add(columnEntity);
        }
        tableEntity.setColumns(columsList);

        // 没主键
        if (tableEntity.getPk() == null) {
            final ColumnEntity columnEntity = new ColumnEntity();
            columnEntity.setAttrType(config.getIdType());
            
            tableEntity.setPk(columnEntity);
        }

        //设置velocity资源加载器
        Properties prop = new Properties();
        prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        Velocity.init(prop);
        String mainPath = config.getMainPath();
        mainPath = StringUtils.isBlank(mainPath) ? "io.renren" : mainPath;
        //封装模板数据
        Map<String, Object> map = new HashMap<>();

        // 实体注解
        final HashSet<String> entityAnnotation = new HashSet<>();

        // 基类
        if (config.getExtendsEntityPath() == null) {
            map.put("extends", "");
            map.put("implements", " implements Serializable");
            importSet.add("\nimport java.io.Serializable;");
        } else {
            final String baseEntity = config.getExtendsEntityPath().substring(config.getExtendsEntityPath().lastIndexOf(".") + 1);
            map.put("extends", " extends " + baseEntity);
            map.put("implements", "");
            importSet.add("\nimport " + config.getExtendsEntityPath() + ";");

            entityAnnotation.add("\n@ToString(callSuper = true)\n" +
                    "@EqualsAndHashCode(callSuper = true)");
        }
        
        map.put("tableName", tableEntity.getTableName());
        map.put("comments", tableEntity.getTableComment());
        map.put("pk", tableEntity.getPk());
        map.put("className", tableEntity.getClassName());
        map.put("classname", tableEntity.getClassNameLowercase());
        map.put("classNameUppercase", tableEntity.getClassName().toUpperCase(Locale.ROOT));
        map.put("pathName", tableEntity.getClassNameLowercase().toLowerCase());
        map.put("columns", tableEntity.getColumns());
        map.put("hasBigDecimal", hasBigDecimal);
        map.put("hasList", hasList);
        map.put("mainPath", mainPath);
        map.put("package", config.getPackages());
        map.put("moduleName", moduleName);
        map.put("author", config.getAuthor());
        map.put("email", config.getEmail());
        map.put("datetime", DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
        map.put("importStr", String.join("", importSet));
        map.put("entityAnnotation", String.join("", entityAnnotation));

        return new VelocityContext(map);
    }

    /**
     * 列名转换成Java属性名
     */
    public static String columnToJava(String columnName) {
        return WordUtils.capitalizeFully(columnName, new char[]{'_'}).replace("_", "");
    }

    /**
     * 表名转换成Java类名
     */
    public static String tableToJava(String tableName, List<String> tablePrefixArray) {
        if (null != tablePrefixArray && tablePrefixArray.size() > 0) {
            for (String tablePrefix : tablePrefixArray) {
                if (tableName.startsWith(tablePrefix)) {
                    tableName = tableName.replaceFirst(tablePrefix, "");
                }
            }
        }
        return columnToJava(tableName);
    }

    /**
     * 获取文件名
     */
    public static String getFileName(String template, String className, String packageName, String moduleName) {
        String packagePath = "main" + File.separator + "java" + File.separator;
        if (StringUtils.isNotBlank(packageName)) {
            packagePath += packageName.replace(".", File.separator) + File.separator + moduleName + File.separator;
        }
        if (template.contains("MongoChildrenEntity.java.vm")) {
            return packagePath + "entity" + File.separator + "inner" + File.separator + currentTableName + File.separator + splitInnerName(className) + "InnerEntity.java";
        }
        if (template.contains("Entity.java.vm") || template.contains("MongoEntity.java.vm")) {
            return packagePath + "entity" + File.separator + className + "Entity.java";
        }
        if (template.contains("SearchDto.java.vm")) {
            return packagePath + "dto" + File.separator + className + "Search.java";
        }

        if (template.contains("Repository.java.vm")) {
            return packagePath + "dao" + File.separator + className + "Repository.java";
        }
        
        if (template.contains("Mapper.java.vm")) {
            return packagePath + "mapper" + File.separator + className + "Mapper.java";
        }

        if (template.contains("Service.java.vm")) {
            return packagePath + "service" + File.separator + className + "Service.java";
        }

        if (template.contains("ServiceImpl.java.vm")) {
            return packagePath + "service" + File.separator + "impl" + File.separator + className + "ServiceImpl.java";
        }

        if (template.contains("Controller.java.vm")) {
            return packagePath + "controller" + File.separator + className + "Controller.java";
        }

        if (template.contains("Dao.xml.vm")) {
            return "main" + File.separator + "resources" + File.separator + "mapper" + File.separator + moduleName + File.separator + className + "Dao.xml";
        }

        if (template.contains("menu.sql.vm")) {
            return className.toLowerCase() + "_menu.sql";
        }

        if (template.contains("index.vue.vm")) {
            return "main" + File.separator + "resources" + File.separator + "src" + File.separator + "views" + File.separator + "modules" +
                    File.separator + moduleName + File.separator + className.toLowerCase() + ".vue";
        }

        if (template.contains("add-or-update.vue.vm")) {
            return "main" + File.separator + "resources" + File.separator + "src" + File.separator + "views" + File.separator + "modules" +
                    File.separator + moduleName + File.separator + className.toLowerCase() + "-add-or-update.vue";
        }

        return null;
    }

    private static String splitInnerName(String name) {
        name = name.replaceAll("\\.", "_");
        return name;
    }
}
